package com.fileupload.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.function.Supplier;

/**
 * 重试机制工具类<br>
 * 提供带指数退避策略的重试机制，适用于网络操作、文件上传等可能暂时性失败的场景<br>
 * 支持自定义最大重试次数、初始延迟、退避因子和异常处理
 * 
 * @author 小乙
 * @version 1.0
 * @since 2024-01-01
 */
public class RetryUtil {
    
    /**
     * 日志记录器
     */
    private static final Logger logger = LoggerFactory.getLogger(RetryUtil.class);
    
    /**
     * 执行带重试机制的操作
     * <p>
     * 该方法实现了通用的重试机制，适用于可能暂时性失败的操作。
     * 当操作执行失败时，会在指定的延迟时间后进行重试，直到成功或达到最大重试次数。
     * </p>
     * 
     * @param <T> 返回值类型
     * @param action 要执行的操作，封装在Supplier中
     * @param maxRetries 最大重试次数
     * @param delayMs 重试间隔时间(毫秒)
     * @return 操作成功时的返回结果
     * @throws Exception 如果达到最大重试次数后仍然失败，抛出最后捕获的异常
     * @throws FileUploadException 当重试等待被中断时抛出
     */
    public static <T> T executeWithRetry(Supplier<T> action, int maxRetries, long delayMs) throws Exception {
        int attempts = 0;
        Exception lastException = null;
        
        while (attempts <= maxRetries) {
            try {
                logger.debug("执行操作, 第 {} 次尝试", attempts + 1);
                return action.get();
            } catch (Exception e) {
                lastException = e;
                attempts++;
                
                if (attempts > maxRetries) {
                    logger.error("操作执行失败，已达到最大重试次数: {}", maxRetries, e);
                    throw e;
                }
                
                logger.warn("操作执行失败，将在 {} 毫秒后进行第 {} 次重试: {}", 
                        delayMs, attempts, e.getMessage());
                
                try {
                    Thread.sleep(delayMs);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    logger.error("重试等待被中断", ie);
                    throw new FileUploadException("RETRY_INTERRUPTED", "重试等待被中断", ie);
                }
            }
        }
        
        // 理论上不会到达这里，但为了代码完整性
        throw new FileUploadException("MAX_RETRIES_EXCEEDED", "达到最大重试次数", lastException);
    }
    
    /**
     * 执行带重试机制的无返回值操作
     * <p>
     * 该方法是对带返回值的{@link #executeWithRetry(Supplier, int, long)}的简化封装，
     * 适用于无需返回值的操作重试场景。
     * </p>
     * 
     * @param action 要执行的无返回值操作，封装在Runnable中
     * @param maxRetries 最大重试次数
     * @param delayMs 重试间隔时间(毫秒)
     * @throws Exception 如果达到最大重试次数后仍然失败，抛出最后捕获的异常
     * @throws FileUploadException 当重试等待被中断时抛出
     */
    public static void executeWithRetry(Runnable action, int maxRetries, long delayMs) throws Exception {
        executeWithRetry(() -> {
            action.run();
            return null;
        }, maxRetries, delayMs);
    }
    
    /**
     * 执行带指数退避策略的重试操作
     * <p>
     * 该方法实现了带指数退避策略的重试机制，适用于网络操作、文件上传等场景。
     * 每次重试的等待时间会按照指定的乘数增加（指数增长），但不会超过最大延迟时间，
     * 这样可以有效避免在系统繁忙时持续发送请求导致的雪崩效应。
     * </p>
     * 
     * @param <T> 返回值类型
     * @param action 要执行的操作，封装在Supplier中
     * @param maxRetries 最大重试次数
     * @param initialDelayMs 初始延迟时间(毫秒)
     * @param maxDelayMs 最大延迟时间(毫秒)，防止延迟时间过长
     * @param multiplier 延迟乘数，用于计算下一次的延迟时间
     * @return 操作成功时的返回结果
     * @throws Exception 如果达到最大重试次数后仍然失败，抛出最后捕获的异常
     * @throws FileUploadException 当重试等待被中断时抛出
     */
    public static <T> T executeWithExponentialBackoff(Supplier<T> action, int maxRetries, long initialDelayMs, 
                                                     long maxDelayMs, double multiplier) throws Exception {
        int attempts = 0;
        Exception lastException = null;
        long currentDelay = initialDelayMs;
        
        while (attempts <= maxRetries) {
            try {
                logger.debug("执行操作, 第 {} 次尝试", attempts + 1);
                return action.get();
            } catch (Exception e) {
                lastException = e;
                attempts++;
                
                if (attempts > maxRetries) {
                    logger.error("操作执行失败，已达到最大重试次数: {}", maxRetries, e);
                    throw e;
                }
                
                logger.warn("操作执行失败，将在 {} 毫秒后进行第 {} 次重试: {}", 
                        currentDelay, attempts, e.getMessage());
                
                try {
                    Thread.sleep(currentDelay);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    logger.error("重试等待被中断", ie);
                    throw new FileUploadException("RETRY_INTERRUPTED", "重试等待被中断", ie);
                }
                
                // 更新延迟时间，使用指数退避策略
                long nextDelay = (long) (currentDelay * multiplier);
                currentDelay = Math.min(nextDelay, maxDelayMs);
            }
        }
        
        throw new FileUploadException("MAX_RETRIES_EXCEEDED", "达到最大重试次数", lastException);
    }
}